package generate

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/PuerkitoBio/goquery"
	"github.com/briandowns/spinner"
	"github.com/fatih/color"
)

var (
	REPL    = []byte("\n")
	Digital = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
	Synmb   = []string{
		"~",
		"!",
		"@",
		"#",
		"$",
		"%",
	}
	LastGenreate = []string{}
	letterRunes  = []rune("abc!@d#e$fg%h^i&j*k(l)m_n+o-p=q~r1s2t3u4v5w6x7y8z9A0B{C}D1E2F3G4H5I6J7K8L9M0NOPQRSTUVWXYZ01234567892345678987651325")
)

func GenBy(raw []string, num int) (out []string) {
	for c := 0; c < num; c++ {
		if c == 0 {
			for _, w := range raw {
				out = append(out, w)
			}
		} else {
			newtmp := []string{}
			for _, pre := range out {
				if len(pre) < c {
					continue
				}
				for _, w := range raw {
					newtmp = append(newtmp, pre+w)
				}
			}
			out = append(out, newtmp...)
		}
	}
	return
}

func Genrandom(num int) (out []string) {
	outs := make(map[string]bool)
	for {
		c := RandStringRunes(num)
		if _, ok := outs[c]; ok {
			continue
		} else {
			out = append(out, c)
			outs[c] = true
		}
		if len(out) >= 1000 {
			break
		}
	}
	return
}

func StartSpin(suf string, doSome func() int) (s *spinner.Spinner) {
	text := color.New(color.FgYellow, color.Bold).Sprint("[+]  ") + suf
	fmt.Println(text)
	s = spinner.New(spinner.CharSets[7], 100*time.Millisecond) // Build our new spinner
	s.Suffix = suf
	s.Start() // Start the spinner
	i := doSome()
	s.Stop()
	fmt.Println(color.New(color.FgGreen, color.Bold).Sprint("[ok] ")+suf+"Finish:", i)
	return
}

// D2, S1,P
func GeneratePassword(output string, modeStr string, extendFile string) (Count int) {

	modes := strings.Split(modeStr, ",")

	tmp := []string{}
	// now := 0
	// last := 0
	for _, mode := range modes {
		mode = strings.TrimSpace(mode)
		switch mode[0:1] {
		case "D":
			StartSpin("Generate by mode: "+mode, func() int {
				num, _ := strconv.Atoi(mode[1:])
				keys := GenBy(Digital, num)
				if len(tmp) == 0 {
					tmp = append(tmp, keys...)
				} else {
					newtmp := []string{}
					for _, w := range tmp {
						for _, k := range keys {
							newtmp = append(newtmp, w+k)
						}
					}
					tmp = newtmp
				}

				return len(keys)
			})
			// last = now
			// now = len(tmp)
		case "S":
			StartSpin("Generate by mode: "+mode, func() int {
				num, _ := strconv.Atoi(mode[1:])
				keys := GenBy(Synmb, num)
				if len(tmp) == 0 {
					tmp = append(tmp, keys...)
				} else {
					newtmp := []string{}
					for _, w := range tmp {
						for _, k := range keys {
							newtmp = append(newtmp, w+k)
						}
					}
					tmp = newtmp
				}

				return len(keys)
			})

		case "R":
			StartSpin("Generate by mode: "+mode, func() int {
				num, _ := strconv.Atoi(mode[1:])
				keys := Genrandom(num)
				if len(tmp) == 0 {
					tmp = append(tmp, keys...)
				} else {
					newtmp := []string{}
					for _, w := range tmp {
						for _, k := range keys {
							newtmp = append(newtmp, w+k)
						}
					}
					tmp = newtmp
				}
				return len(keys)
			})

		case "W":
			uri := GetUri()
			keys := GetTextFromUrl(uri)
			StartSpin("Generate by mode: "+mode, func() int {
				if len(tmp) == 0 {
					tmp = append(tmp, keys...)
				} else {
					newtmp := []string{}
					for _, w := range tmp {
						for _, k := range keys {
							newtmp = append(newtmp, w+k)
						}
					}
					// if len(newtmp) > 0 {
					tmp = newtmp
					// }
				}

				return len(keys)
			})

		case "P":
			if extendFile == "" {
				break
			}
			StartSpin("Generate by mode: "+mode, func() int {
				as := []string{}
				if buf, err := ioutil.ReadFile(extendFile); err == nil {
					for _, word := range bytes.Split(buf, REPL) {
						// oneline.Write(bytes.TrimSpace(word))
						wo := string(bytes.TrimSpace(word))
						if wo == "" {
							continue
						}
						as = append(as, wo)
					}
				}
				if len(tmp) == 0 {
					tmp = append(tmp, as...)
				} else {
					newtmp := []string{}
					for _, w := range tmp {
						for _, k := range as {
							newtmp = append(newtmp, w+k)
						}
					}
					tmp = newtmp
				}

				return len(as)
			})
		}
	}
	defer func() {
		LastGenreate = tmp
	}()

	outputFp, err := os.Create(output)
	if err != nil {
		log.Println("err output:", err)
		return
	}
	defer outputFp.Close()
	for _, w := range tmp {
		outputFp.Write([]byte(w + "\n"))
	}
	return len(tmp)
}

func init() {
	rand.Seed(time.Now().UnixNano())
}

func RandStringRunes(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

// lenLess : 5, 9
func GetTextFromUrl(url string) (o []string) {
	// if lenLess >= lenMax {
	// 	lenLess = 5
	// 	lenMax = 19
	// }
	response, err := http.Get(url)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer response.Body.Close()

	// responseData, err := ioutil.ReadAll(response.Body)
	// fmt.Println(responseData)
	// if err != nil {
	// 	fmt.Println(err)
	// 	return
	// }

	scanner := bufio.NewScanner(response.Body)
	scanner.Split(bufio.ScanRunes)
	var buf bytes.Buffer
	for scanner.Scan() {
		buf.WriteString(scanner.Text())
	}

	doc, _ := goquery.NewDocumentFromReader(strings.NewReader(buf.String()))
	bufs := ""
	doc.Find("span").Each(func(i int, el *goquery.Selection) {
		bufs += " " + el.Text()
	})
	doc.Find("p").Each(func(i int, el *goquery.Selection) {
		bufs += " " + el.Text()
	})
	doc.Find("h1").Each(func(i int, el *goquery.Selection) {
		bufs += " " + el.Text()
	})

	doc.Find("h4").Each(func(i int, el *goquery.Selection) {
		bufs += " " + el.Text()
	})

	doc.Find("h2").Each(func(i int, el *goquery.Selection) {
		bufs += " " + el.Text()
	})
	doc.Find("h3").Each(func(i int, el *goquery.Selection) {
		bufs += " " + el.Text()
	})

	doc.Find("div").Each(func(i int, el *goquery.Selection) {
		bufs += " " + el.Text()
	})

	fmt.Println(bufs)
	wordsRe := regexp.MustCompile(`\w+`)
	outs := make(map[string]bool)
	for _, word := range wordsRe.FindAllString(bufs, -1) {
		if _, ok := outs[word]; ok {
			continue
		} else {
			outs[word] = true
			// fmt.Println(word)
			if len(word) > 3 {
				o = append(o, word)
			}
		}
	}
	return
}
